//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2005-2007
//
// Waypoints, tracks and routes editor.
//

# include "StdAfx.h"
# include "Resource.h"
# include "EditWTR.h"
# include "Map.h"
# include "RgnProperties.h"
# include "ParseHelpers.h"
# include "PrintUnits.h"
# include "ListView.h"
# include "Settings.h"
# include "fast_floor.h"
# include "Globals.h"

const int c_nWaypointsTab = 0;
const int c_nTracksTab    = 1;
const int c_nRoutesTab    = 2;

const UINT IDC_WAYPOINTS_LIST = IDC_TAB + 1 + c_nWaypointsTab;
const UINT IDC_TRACKS_LIST    = IDC_TAB + 1 + c_nTracksTab;
const UINT IDC_ROUTES_LIST    = IDC_TAB + 1 + c_nRoutesTab;

const size_t c_cTabs = 3;
static size_t g_cTabSelected = 0;

const int c_nListMargin = 3; // In pixels.

/////////////////////////////////////////////////////////

enum wtr_sorting_t {
	srtText = 0,
	srtInteger,
	srtFloat,

	srtWptAltitude,
	srtWptDepth,
	srtWptProximity,
	srtWptTime,

	srtTrkStartTime,
	srtTrkElapsedTime,
	srtTrkLength,
	srtTrkArea,

	srtRteLength,
};

struct lv_data_t {
	HWND hList;
	int  iSortColumn;
	bool bAccent;
	std::vector<wtr_sorting_t> sortings;

	lv_data_t () : hList (NULL), iSortColumn (-1), bAccent (true) {}
};

static lv_data_t g_lv_data [c_cTabs];

static
void _InitColumnData (size_t _cTab, size_t _cColumns, HWND _hList) {
	g_lv_data [_cTab].hList = _hList;

	g_lv_data [_cTab].sortings.resize (_cColumns);
	for (size_t c = 0; c < _cColumns; ++ c)
		g_lv_data [_cTab].sortings [c] = srtText;
}

static
int _Float2Diff (float _fDiff) {
	if (_fDiff == 0)
		return 0;
	return _fDiff > 0 ? 1 : -1;
}

//////////////////////////////////////////////////////////

static
DATE _GetWptTime (const rgn_t & _rgn) {
	if (! _rgn.pExtra)
		return 0;
	const waypoint_info_t * pWptExtra = _rgn.pExtra->wtr.wpt ();
	if ((pWptExtra->wFlags & waypoint_info_t::defTime) == 0)
		return 0;
	return pWptExtra->dtTime;
}

static
float _GetWptAltitude (const rgn_t & _rgn) {
	if (! _rgn.pExtra)
		return 0;
	const waypoint_info_t * pWptExtra = _rgn.pExtra->wtr.wpt ();
	if ((pWptExtra->wFlags & waypoint_info_t::defAltitude) == 0)
		return 0;
	return pWptExtra->fAltitude;
}

static
float _GetWptDepth (const rgn_t & _rgn) {
	if (! _rgn.pExtra)
		return 0;
	const waypoint_info_t * pWptExtra = _rgn.pExtra->wtr.wpt ();
	if ((pWptExtra->wFlags & waypoint_info_t::defDepth) == 0)
		return 0;
	return pWptExtra->fDepth;
}

static
float _GetWptProximity (const rgn_t & _rgn) {
	if (! _rgn.pExtra)
		return 0;
	const waypoint_info_t * pWptExtra = _rgn.pExtra->wtr.wpt ();
	if ((pWptExtra->wFlags & waypoint_info_t::defProximity) == 0)
		return 0;
	return pWptExtra->fProximity;
}

//////////////////////////////////////////////////////////

static
DATE _GetTrkStartTime (const rgn_t & _rgn) {
	if (! _rgn.pExtra)
		return 0;
	const trackpoints_t & tpoints = _rgn.elements [0].pExtra->TrackPoints;
	if (tpoints.empty ())
		return 0;
	if ((tpoints [0].wFlags & trackpoint_info_t::defTime) == 0)
		return 0;
	return tpoints [0].dtTime;
}

static
DATE _GetTrkElapsedTime (const rgn_t & _rgn) {
	if (! _rgn.pExtra)
		return 0;
	const trackpoints_t & tpoints = _rgn.elements [0].pExtra->TrackPoints;
	if (tpoints.empty ())
		return 0;
	if ((tpoints [0                  ].wFlags & trackpoint_info_t::defTime) == 0)
		return 0;
	if ((tpoints [tpoints.size () - 1].wFlags & trackpoint_info_t::defTime) == 0)
		return 0;
	return tpoints [tpoints.size () - 1].dtTime - tpoints [0].dtTime;
}

static
DATE _GetPolyLength (const rgn_t & _rgn) {
	if (_rgn.elements.empty ())
		return 0;
	return GetLength (_rgn.elements [0].points);
}

static
DATE _GetPolyArea (const rgn_t & _rgn) {
	if (_rgn.elements.empty ())
		return 0;
	return GetArea (_rgn.elements [0].points);
}

/////////////////////////////////////////////////////////

static
int CALLBACK _CompareItems (LPARAM _lParam1, LPARAM _lParam2, LPARAM _lParamSort) {
	lv_data_t & lv_data = * reinterpret_cast<lv_data_t *> (_lParamSort);

	const rgn_t & rgn1 = * reinterpret_cast<const rgn_t *> (GetLParam (lv_data.hList, _lParam1));
	const rgn_t & rgn2 = * reinterpret_cast<const rgn_t *> (GetLParam (lv_data.hList, _lParam2));

	const wtr_sorting_t srt = lv_data.sortings [lv_data.iSortColumn];

	char str1 [64], str2 [64];
	ListView_GetItemText (lv_data.hList, _lParam1, lv_data.iSortColumn, str1, 64); 
	ListView_GetItemText (lv_data.hList, _lParam2, lv_data.iSortColumn, str2, 64);

	int nDiff = 0;
	switch (srt) {
		default: assert (false);
		case srtText:           nDiff = ::lstrcmpi (str1, str2); break;
		case srtInteger:        nDiff = ParseDecU (str1, str1 + 64) - ParseDecU (str2, str2 + 64); break;
		case srtFloat:          nDiff = _Float2Diff (ParseReal (str1, str1 + 64) - ParseReal (str2, str2 + 64)); break;

		case srtWptAltitude:    nDiff = _Float2Diff (_GetWptAltitude    (rgn1) - _GetWptAltitude    (rgn2)); break;
		case srtWptDepth:       nDiff = _Float2Diff (_GetWptDepth       (rgn1) - _GetWptDepth       (rgn2)); break;
		case srtWptProximity:   nDiff = _Float2Diff (_GetWptProximity   (rgn1) - _GetWptProximity   (rgn2)); break;
		case srtWptTime:        nDiff = _Float2Diff (_GetWptTime        (rgn1) - _GetWptTime        (rgn2)); break;

		case srtTrkStartTime:   nDiff = _Float2Diff (_GetTrkStartTime   (rgn1) - _GetTrkStartTime   (rgn2)); break;
		case srtTrkElapsedTime: nDiff = _Float2Diff (_GetTrkElapsedTime (rgn1) - _GetTrkElapsedTime (rgn2)); break;
		case srtTrkLength:      nDiff = _Float2Diff (_GetPolyLength     (rgn1) - _GetPolyLength     (rgn2)); break;
		case srtTrkArea:        nDiff = _Float2Diff (_GetPolyArea       (rgn1) - _GetPolyArea       (rgn2)); break;

		case srtRteLength:      nDiff = _Float2Diff (_GetPolyLength     (rgn1) - _GetPolyLength     (rgn2)); break;
	}

	return lv_data.bAccent ? nDiff : -nDiff;
}

/////////////////////////////////////////////////////////

enum {
	lvWptName        = 0,
	lvWptPosition    = 1,
	lvWptAltitude    = 2,
	lvWptDepth       = 3,
	lvWptTemperature = 4,
	lvWptProximity   = 5,
	lvWptShow        = 6,
	lvWptTime        = 7,

	lvWptMax
};

static
void _FillWaypoints (HWND _hList) {
	ListView_DeleteAllItems (_hList);

	// Get time zone bias + daylight saving time bias.
	TIME_ZONE_INFORMATION tzi;
	if (::GetTimeZoneInformation (& tzi) == TIME_ZONE_ID_DAYLIGHT)
		tzi.Bias += tzi.DaylightBias;

	bool bEmptyColumn [lvWptMax];
	int nColumn;
	for (nColumn = 0; nColumn < lvWptMax; ++ nColumn)
		bEmptyColumn [nColumn] = true;

	CCritSec::CLock lock (g_cs);

	const layer_t & layer = g_map.layers [rkPoint];
	const rgns_t & rgns = layer.rgns;
	for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsWaypoint ())
			continue;

		if (rgn.origins.empty ())
			continue;
		const point_t & point = rgn.origins [0].point;

		const waypoint_info_t * const pWptExtra = rgn.pExtra->wtr.wpt ();
		assert (pWptExtra);

		// Name.
		size_t cItem = 0;
		InsertItem (_hList, cItem, reinterpret_cast<LPARAM> (& rgn), "%s", rgn.strLabel.c_str ());

		// Position.
		char strPosition [64];
		PrintPosition (strPosition, point);
		SetSubItemText (_hList, cItem, lvWptPosition, "%s", strPosition);
		bEmptyColumn [lvWptPosition] = false;

		// Altitude.
		if (pWptExtra->wFlags & waypoint_info_t::defAltitude) {
			char strAltitude [32];
			char strAltitudeUnit [16];
			PrintElevation (strAltitude, strAltitudeUnit, pWptExtra->fAltitude, unMeters, false);
		
			SetSubItemText (_hList, cItem, lvWptAltitude, "%s %s", strAltitude, strAltitudeUnit);
			bEmptyColumn [lvWptAltitude] = false;
		}

		// Depth.
		if (pWptExtra->wFlags & waypoint_info_t::defDepth) {
			char strDepth [32];
			char strDepthUnit [16];
			PrintDepth (strDepth, strDepthUnit, pWptExtra->fDepth);
		
			SetSubItemText (_hList, cItem, lvWptDepth, "%s %s", strDepth, strDepthUnit);
			bEmptyColumn [lvWptDepth] = false;
		}

		// Temperature.
		if (pWptExtra->wFlags & waypoint_info_t::defTemperature) {
			char strTemperature [32];
			char strTemperatureUnit [16];
			PrintTemperature (strTemperature, strTemperatureUnit, pWptExtra->fTemperature);
		
			SetSubItemText (_hList, cItem, lvWptTemperature, "%s%s", strTemperature, strTemperatureUnit);
			bEmptyColumn [lvWptTemperature] = false;
		}

		// Proximity.
		if (pWptExtra->wFlags & waypoint_info_t::defProximity) {
			char strProximity [32];
			char strProximityUnit [32];
			PrintDistance (strProximity, strProximityUnit, pWptExtra->fProximity);
		
			SetSubItemText (_hList, cItem, lvWptProximity, "%s %s", strProximity, strProximityUnit);
			bEmptyColumn [lvWptProximity] = false;
		}

		// Show.
		switch (pWptExtra->GetShow ()) {
			default:
			case waypoint_info_t::showName:        SetSubItemText (_hList, cItem, lvWptShow, "symbol & name"); break;
			case waypoint_info_t::showDescription: SetSubItemText (_hList, cItem, lvWptShow, "symbol & description"); break;
			case waypoint_info_t::showSymbol:      SetSubItemText (_hList, cItem, lvWptShow, "symbol only"); break;
			case waypoint_info_t::showHide:        SetSubItemText (_hList, cItem, lvWptShow, "hide"); break;
		}
		bEmptyColumn [lvWptShow] = false;

		// Time.
		if (pWptExtra->wFlags & waypoint_info_t::defTime) {
			SYSTEMTIME st;
			::VariantTimeToSystemTime (pWptExtra->dtTime - float (tzi.Bias)/(24*60), & st);

			char strDate [32];
			if (! ::GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, & st, NULL, strDate, sizeof (strDate)/sizeof (strDate [0])))
				strDate [0] = '\0';
			char strTime [32];
			if (! ::GetTimeFormat (LOCALE_USER_DEFAULT, 0, & st, NULL, strTime, sizeof (strTime)/sizeof (strTime [0])))
				strTime [0] = '\0';

			SetSubItemText (_hList, cItem, lvWptTime, "%s %s", strDate, strTime);
			bEmptyColumn [lvWptTime] = false;
		}

		++ cItem;
	}

	// Delete empty columns.
	for (nColumn = lvWptMax - 1; nColumn > 0; -- nColumn) {
		if (bEmptyColumn [nColumn]) {
			ListView_DeleteColumn (_hList, nColumn);
			g_lv_data [c_nWaypointsTab].sortings.erase (g_lv_data [c_nWaypointsTab].sortings.begin () + nColumn);
		}
	}

	// Set column widths.
	AutoSizeColumns (_hList);
	if (ListView_GetColumnWidth (_hList, lvWptName) > 200)
		ListView_SetColumnWidth (_hList, lvWptName, 200);

	// Sort items.
	lv_data_t & lv_data = g_lv_data [c_nWaypointsTab];
	if (lv_data.iSortColumn != -1)
		ListView_SortItemsEx (_hList, _CompareItems, reinterpret_cast<LPARAM> (& lv_data));
}

/////////////////////////////////////////////////////////

enum {
	lvTrkName        = 0,
	lvTrkPoints      = 1,
	lvTrkStartTime   = 2,
	lvTrkElapsedTime = 3,
	lvTrkLength      = 4,
	lvTrkArea        = 5,
	lvTrkAveSpeed    = 6,

	lvTrkMax
};

static
void _FillTracks (HWND _hList) {
	ListView_DeleteAllItems (_hList);

	// Get time zone bias + daylight saving time bias.
	TIME_ZONE_INFORMATION tzi;
	if (::GetTimeZoneInformation (& tzi) == TIME_ZONE_ID_DAYLIGHT)
		tzi.Bias += tzi.DaylightBias;

	bool bEmptyColumn [lvTrkMax];
	int nColumn;
	for (nColumn = 0; nColumn < lvTrkMax; ++ nColumn)
		bEmptyColumn [nColumn] = true;

	CCritSec::CLock lock (g_cs);

	const layer_t & layer = g_map.layers [rkPolyline];
	const rgns_t & rgns = layer.rgns;
	for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsTrack ())
			continue;

		if (rgn.elements.empty ())
			continue;
		const points_t & points = rgn.elements [0].points;
		const size_t cPoints = points.size ();

		const track_info_t * const pTrkExtra = rgn.pExtra->wtr.track ();
		assert (pTrkExtra);
		const trackpoints_t & TrackPoints = rgn.elements [0].pExtra->TrackPoints;

		// Name.
		size_t cItem = 0;
		InsertItem (_hList, cItem, reinterpret_cast<LPARAM> (& rgn), "%s", rgn.strLabel.c_str ());

		// Points.
		SetSubItemText (_hList, cItem, lvTrkPoints, "%d", cPoints);
		bEmptyColumn [lvTrkPoints] = false;

		// Start time.
		if (! TrackPoints.empty () && TrackPoints [0].wFlags & trackpoint_info_t::defTime) {
			DATE dtLocalTime = TrackPoints [0].dtTime - float (tzi.Bias)/(24*60);

			SYSTEMTIME st;
			::VariantTimeToSystemTime (dtLocalTime, & st);

			char strDate [32];
			if (! ::GetDateFormat (LOCALE_USER_DEFAULT, DATE_SHORTDATE, & st, NULL, strDate, sizeof (strDate)/sizeof (strDate [0])))
				strDate [0] = '\0';
			char strTime [32];
			if (! ::GetTimeFormat (LOCALE_USER_DEFAULT, 0, & st, NULL, strTime, sizeof (strTime)/sizeof (strTime [0])))
				strTime [0] = '\0';

			SetSubItemText (_hList, cItem, lvTrkStartTime, "%s %s", strDate, strTime);
			bEmptyColumn [lvTrkStartTime] = false;
		}

		// Elapsed time.
		double fElapsedTime = 0;
		if (
			! TrackPoints.empty () &&
			TrackPoints [0                      ].wFlags & trackpoint_info_t::defTime &&
			TrackPoints [TrackPoints.size () - 1].wFlags & trackpoint_info_t::defTime
		) {
			fElapsedTime = (TrackPoints [TrackPoints.size () - 1].dtTime - TrackPoints [0].dtTime)*24*60*60;
			const int nSecond = fast_floor_d (fElapsedTime + .5);
			const int nMinute = nSecond/60;
			const int nHour   = nMinute/60;

			SetSubItemText (_hList, cItem, lvTrkElapsedTime, "%02d:%02d:%02d", nHour, nMinute - nHour*60, nSecond - nMinute*60);
			bEmptyColumn [lvTrkElapsedTime] = false;
		}

		// Length.
		const float fLength = GetLength (points)*40e6/360;

		char strDistance [32];
		char strDistanceUnits [16];
		PrintDistance (strDistance, strDistanceUnits, fLength);

		SetSubItemText (_hList, cItem, lvTrkLength, "%s %s", strDistance, strDistanceUnits);
		bEmptyColumn [lvTrkLength] = false;

		// Area.
		char strArea [32];
		char strAreaUnits [16];
		PrintArea (strArea, strAreaUnits, GetArea (points)*40e6*40e6/360/360*g_map.fCosY);

		SetSubItemText (_hList, cItem, lvTrkArea, "%s %s", strArea, strAreaUnits);
		bEmptyColumn [lvTrkArea] = false;

		// Average speed.
		double fAveSpeed = 0;
		bool bAveSpeedDefined = false;
		if (fElapsedTime > 0) {
			fAveSpeed = 3.6*fLength/fElapsedTime;
			bAveSpeedDefined = true;
		} else {
			const size_t cExtraPoints = TrackPoints.size ();
			size_t cN = 0;
			for (size_t c = 0; c < cExtraPoints; ++ c) {
				if (TrackPoints [c].wFlags & trackpoint_info_t::defSpeed) {
					fAveSpeed += TrackPoints [c].fSpeed;
					++ cN;
				}
			}
			if (cN > 0) {
				fAveSpeed /= cN;
				bAveSpeedDefined = true;
			}
		}
		if (bAveSpeedDefined) {
			char strAveSpeed [32];
			char strSpeedUnits [16];
			PrintSpeed (strAveSpeed, strSpeedUnits, fAveSpeed);

			SetSubItemText (_hList, cItem, lvTrkAveSpeed, "%s %s", strAveSpeed, strSpeedUnits);
			bEmptyColumn [lvTrkAveSpeed] = false;
		}

		++ cItem;
	}

	// Delete empty columns.
	for (nColumn = lvTrkMax - 1; nColumn > 0; -- nColumn) {
		if (bEmptyColumn [nColumn]) {
			ListView_DeleteColumn (_hList, nColumn);
			g_lv_data [c_nTracksTab].sortings.erase (g_lv_data [c_nTracksTab].sortings.begin () + nColumn);
		}
	}

	// Set column widths.
	AutoSizeColumns (_hList);
	if (ListView_GetColumnWidth (_hList, lvTrkName) > 200)
		ListView_SetColumnWidth (_hList, lvTrkName, 200);

	// Sort items.
	lv_data_t & lv_data = g_lv_data [c_nTracksTab];
	if (lv_data.iSortColumn != -1)
		ListView_SortItemsEx (_hList, _CompareItems, reinterpret_cast<LPARAM> (& lv_data));
}

/////////////////////////////////////////////////////////

enum {
	lvRteName        = 0,
	lvRtePoints      = 1,
	lvRteLength      = 2,

	lvRteMax
};

static
void _FillRoutes (HWND _hList) {
	ListView_DeleteAllItems (_hList);

	CCritSec::CLock lock (g_cs);

	const layer_t & layer = g_map.layers [rkPolyline];
	const rgns_t & rgns = layer.rgns;
	for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
		const rgn_t & rgn = * iRgn;
		if (rgn.IsDeleted () || ! rgn.IsRoute ())
			continue;

		if (rgn.elements.empty ())
			continue;
		const points_t & points = rgn.elements [0].points;
		const size_t cPoints = points.size ();

		const route_info_t * const pRteExtra = rgn.pExtra->wtr.route ();
		assert (pRteExtra);

		// Name.
		size_t cItem = 0;
		InsertItem (_hList, cItem, reinterpret_cast<LPARAM> (& rgn), "%s", rgn.strLabel.c_str ());

		// Points.
		SetSubItemText (_hList, cItem, lvRtePoints, "%d", cPoints);
		
		// Length.
		char strDistance [32];
		char strDistanceUnits [16];
		PrintDistance (strDistance, strDistanceUnits, GetLength (points)*40e6/360);

		SetSubItemText (_hList, cItem, lvRteLength, "%s %s", strDistance, strDistanceUnits);
		
		++ cItem;
	}

	// Set column widths.
	AutoSizeColumns (_hList);
	if (ListView_GetColumnWidth (_hList, lvRteName) > 200)
		ListView_SetColumnWidth (_hList, lvRteName, 200);

	// Sort items.
	lv_data_t & lv_data = g_lv_data [c_nRoutesTab];
	if (lv_data.iSortColumn != -1)
		ListView_SortItemsEx (_hList, _CompareItems, reinterpret_cast<LPARAM> (& lv_data));
}

/////////////////////////////////////////////////////////

static
HWND _CreateListView (HWND _hWndParent, const RECT & _rect, UINT _uiID) {
	const HWND hList = ::CreateWindowEx (
		WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL, WS_CHILD | WS_TABSTOP | WS_CLIPSIBLINGS | LVS_REPORT | LVS_SHOWSELALWAYS | LVS_SINGLESEL | LVS_SHAREIMAGELISTS,
		_rect.left, _rect.top, _rect.right - _rect.left, _rect.bottom - _rect.top,
		_hWndParent, reinterpret_cast<HMENU> (_uiID), g_hInst, 0
	);

	ListView_SetExtendedListViewStyleEx (hList, LVS_EX_FULLROWSELECT, LVS_EX_FULLROWSELECT);

	::SetWindowPos (hList, HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW);

	return hList;
}

static
void _SwitchList (HWND _hWnd) {
	for (size_t c = 0; c < c_cTabs; ++ c)
		::ShowWindow (::GetDlgItem (_hWnd, IDC_TAB + 1 + c), c == g_cTabSelected ? SW_SHOW : SW_HIDE);
}

static
INT_PTR CALLBACK _WTRProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			//
			// Prepare tab control.
			//

			const HWND hTab = ::GetDlgItem (_hWnd, IDC_TAB);

			::SetWindowLong (hTab, GWL_STYLE,   WS_CLIPSIBLINGS     | ::GetWindowLong (hTab, GWL_STYLE));
			::SetWindowLong (hTab, GWL_EXSTYLE, WS_EX_CONTROLPARENT | ::GetWindowLong (hTab, GWL_EXSTYLE));
			TabCtrl_SetExtendedStyle (hTab, TCS_EX_FLATSEPARATORS);

			TCITEM tci;
			tci.mask = TCIF_TEXT;

			tci.pszText = "Waypoints";
			tci.cchTextMax = ::strlen (tci.pszText);
			TabCtrl_InsertItem (hTab, c_nWaypointsTab, & tci);

			tci.pszText = "Tracks";
			tci.cchTextMax = ::strlen (tci.pszText);
			TabCtrl_InsertItem (hTab, c_nTracksTab, & tci);

			tci.pszText = "Routes";
			tci.cchTextMax = ::strlen (tci.pszText);
			TabCtrl_InsertItem (hTab, c_nRoutesTab, & tci);

			TabCtrl_SetCurSel (hTab, 0);

			RECT rectList;
			::GetClientRect (hTab, & rectList);
			TabCtrl_AdjustRect (hTab, FALSE, & rectList);
			::InflateRect (& rectList, -c_nListMargin, -c_nListMargin);
			::MapWindowPoints (hTab, _hWnd, (LPPOINT) & rectList, 2);

			//
			// Waypoints.
			//

			const HWND hWaypoints = _CreateListView (_hWnd, rectList, IDC_WAYPOINTS_LIST);

			AddColumn (hWaypoints, lvWptName,        "Name");
			AddColumn (hWaypoints, lvWptPosition,    "Position");
			AddColumn (hWaypoints, lvWptAltitude,    "Altitude",    true);
			AddColumn (hWaypoints, lvWptDepth,       "Depth",       true);
			AddColumn (hWaypoints, lvWptTemperature, "Temperature", true);
			AddColumn (hWaypoints, lvWptProximity,   "Proximity",   true);
			AddColumn (hWaypoints, lvWptShow,        "Show");
			AddColumn (hWaypoints, lvWptTime,        "Date and Time");
			AddColumn (hWaypoints, lvWptMax,         "");

			_InitColumnData (c_nWaypointsTab, lvWptMax, hWaypoints);
			g_lv_data [c_nWaypointsTab].sortings [lvWptAltitude]    = srtWptAltitude;
			g_lv_data [c_nWaypointsTab].sortings [lvWptDepth]       = srtWptDepth;
			g_lv_data [c_nWaypointsTab].sortings [lvWptTemperature] = srtFloat;
			g_lv_data [c_nWaypointsTab].sortings [lvWptProximity]   = srtWptProximity;
			g_lv_data [c_nWaypointsTab].sortings [lvWptTime]        = srtWptTime;

			_FillWaypoints (hWaypoints);

			//
			// Tracks.
			//

			const HWND hTracks = _CreateListView (_hWnd, rectList, IDC_TRACKS_LIST);

			AddColumn (hTracks, lvTrkName,        "Name");
			AddColumn (hTracks, lvTrkPoints,      "Points",       true);
			AddColumn (hTracks, lvTrkStartTime,   "Start time");
			AddColumn (hTracks, lvTrkElapsedTime, "Elapsed time", true);
			AddColumn (hTracks, lvTrkLength,      "Length",       true);
			AddColumn (hTracks, lvTrkArea,        "Area",         true);
			AddColumn (hTracks, lvTrkAveSpeed,    "Avg Speed",    true);
			AddColumn (hTracks, lvTrkMax,         "");

			_InitColumnData (c_nTracksTab, lvTrkMax, hTracks);
			g_lv_data [c_nTracksTab].sortings [lvTrkPoints]      = srtInteger;
			g_lv_data [c_nTracksTab].sortings [lvTrkStartTime]   = srtTrkStartTime;
			g_lv_data [c_nTracksTab].sortings [lvTrkElapsedTime] = srtTrkElapsedTime;
			g_lv_data [c_nTracksTab].sortings [lvTrkLength]      = srtTrkLength;
			g_lv_data [c_nTracksTab].sortings [lvTrkArea]        = srtTrkArea;
			g_lv_data [c_nTracksTab].sortings [lvTrkAveSpeed]    = srtFloat;

			_FillTracks (hTracks);

			//
			// Routes.
			//

			const HWND hRoutes = _CreateListView (_hWnd, rectList, IDC_ROUTES_LIST);

			AddColumn (hRoutes, lvRteName,   "Name");
			AddColumn (hRoutes, lvRtePoints, "Points", true);
			AddColumn (hRoutes, lvRteLength, "Length", true);
			AddColumn (hRoutes, lvRteMax,    "");

			_InitColumnData (c_nRoutesTab, lvRteMax, hRoutes);
			g_lv_data [c_nRoutesTab].sortings [lvRtePoints] = srtInteger;
			g_lv_data [c_nRoutesTab].sortings [lvRteLength] = srtRteLength;

			_FillRoutes (hRoutes);

			// Restore tab selection.
			g_cTabSelected = GetSetting ("WTR_tab", (DWORD) 0);
			if (g_cTabSelected > c_cTabs)
				g_cTabSelected = c_cTabs - 1;

			TabCtrl_SetCurSel (hTab, g_cTabSelected);
			_SwitchList (_hWnd);

			// Restore window position & size.
			RECT rect;
			::GetWindowRect (_hWnd, & rect);
			::SetWindowPos (
				_hWnd, NULL,
				GetSetting ("WTR_X", (DWORD) rect.left),
				GetSetting ("WTR_Y", (DWORD) rect.top),
				GetSetting ("WTR_W", (DWORD) (rect.right - rect.left)),
				GetSetting ("WTR_H", (DWORD) (rect.bottom - rect.top)),
				SWP_NOREDRAW | SWP_NOZORDER
			);

			return TRUE;
		}

	    case WM_NOTIFY: {
            const NMHDR * const pInfo = reinterpret_cast<NMHDR *> (_lParam);
            switch (pInfo->code) {
				case TCN_SELCHANGE: {
					//
					// Tab selected.
					//

					g_cTabSelected = TabCtrl_GetCurSel (::GetDlgItem (_hWnd, IDC_TAB));
					if (g_cTabSelected > c_cTabs)
						g_cTabSelected = c_cTabs - 1;

					SaveSetting ("WTR_tab", (DWORD) g_cTabSelected);

					_SwitchList (_hWnd);

					break;
				}

				case LVN_COLUMNCLICK: {
					const NMLISTVIEW * const pInfo2 = reinterpret_cast<const NMLISTVIEW *> (_lParam);

					lv_data_t & lv_data = g_lv_data [g_cTabSelected];
					if (lv_data.iSortColumn != pInfo2->iSubItem)
						lv_data.iSortColumn = pInfo2->iSubItem;
					else
						lv_data.bAccent = ! lv_data.bAccent;
					
					ListView_SortItemsEx (pInfo->hwndFrom, _CompareItems, reinterpret_cast<LPARAM> (& lv_data));

					break;
				}

				case NM_RETURN:
				case NM_DBLCLK: {
					const HWND hList = pInfo->hwndFrom;

					if (ListView_GetSelectedCount (hList) != 1)
						break;
					const size_t cSelItem = ListView_GetSelectionMark (hList);
					rgn_t * const pRgn = reinterpret_cast<rgn_t *> (GetLParam (hList, cSelItem));

					ShowRgnProperties (_hWnd, * pRgn);

					break;
				}
            }
			break;
        }

		case WM_SIZE: {
			const int nWidth  = LOWORD (_lParam);
			const int nHeight = HIWORD (_lParam);

			// Save windows position & size.
			RECT rect;
			::GetWindowRect (_hWnd, & rect);
			SaveSetting ("WTR_X", (DWORD) rect.left);
			SaveSetting ("WTR_Y", (DWORD) rect.top);
			SaveSetting ("WTR_W", (DWORD) (rect.right - rect.left));
			SaveSetting ("WTR_H", (DWORD) (rect.bottom - rect.top));

			const int nXBorder = ::GetSystemMetrics (SM_CXEDGE);
			const int nYBorder = ::GetSystemMetrics (SM_CYEDGE);

			HDWP hDWP = ::BeginDeferWindowPos (3);

			// Resize tab header.
			const HWND hTab = ::GetDlgItem (_hWnd, IDC_TAB);

			RECT rectTab;
			::GetClientRect (hTab, & rectTab);
			::MapWindowPoints (hTab, _hWnd, (LPPOINT) & rectTab, 2);

			hDWP = ::DeferWindowPos (hDWP, hTab, NULL, 0, 0, nWidth, nHeight - rectTab.top, SWP_NOMOVE | SWP_NOZORDER);
			rectTab.right = rectTab.left + nWidth;
			rectTab.bottom = nHeight;

			// Resize lists.
			for (size_t c = 0; c < c_cTabs; ++ c) {
				const HWND hList = ::GetDlgItem (_hWnd, IDC_TAB + 1 + c);

				RECT rectList;
				::GetClientRect (hList, & rectList);
				::MapWindowPoints (hList, _hWnd, (LPPOINT) & rectList, 2);
				::InflateRect (& rectList, nXBorder, nYBorder);

				hDWP = ::DeferWindowPos (
					hDWP, hList, HWND_TOP, 0, 0,
					rectTab.right - rectTab.left - 2*rectList.left,
					rectTab.bottom - rectTab.top - rectList.top,
					SWP_NOMOVE
				);
			}

			::EndDeferWindowPos (hDWP);

			break;
		}

		case WM_COMMAND: {
			switch (LOWORD (_wParam)) {
				case IDCANCEL:
					::EndDialog (_hWnd, 0);
					return TRUE;
			}
			break;
		}
	}

	return FALSE;
}

void EditWTR () {
	DlgBox (IDD_WTR, g_hWnd, & _WTRProc, 0);
}